自定义Spring bean容器了解一下?实战及原理解读
这是Bella酱的第 49 期分享
作者 | SnoWalker
来源 | http://dwz.date/b23B
作者介绍 | RocketMQ社区2019杰出贡献者
开发中经常有这样的场景:
根据某个类型标识走不同的业务逻辑,通常我们会使用if(type.equals(xxxxx)) 或者 switch语句来进行逻辑处理。
这样做当然是没什么问题的。
当业务逻辑变得越来越复杂,类型标识增多之后,难免会出现if判断增加,或者switch case分支变多,这样的代码往往会过于冗长,代码重复性较大,或者说逼格不够高。
本文介绍一种基于自定义Bean容器的开发方式,消除代码中的判断分支,提升代码可读性。
我们通过一个demo来看如何实现这种编码方式。
定义接口
首先定义一个接口,主要有两个方法:
public interface AbstractService<T> {
/**
* 返回serviceName
* 作为bean选择标识
* @return
*/
String serviceName();
/**
* 具体的service方法
* @param parm
* @return
*/
T execute(Object parm);
}
实现类需要实现serviceName,返回具体的类型,注意不同的bean实现类该返回值不能重复
execute方法为业务方法,这里只是做个示范,实际开发中可以是任意的通用业务方法。
实现接口
接着编写实现类,实现接口
ServiceAImpl标记类型为 ServiceA
@Component
public class ServiceAImpl implements AbstractService<DemoA> {
@Override
public String serviceName() {
return "ServiceA";
}
@Override
public DemoA execute(Object parm) {
System.out.println("ServiceAImpl execute");
return new DemoA().setName("DemoA");
}
}
ServiceBImpl标记类型为 ServiceB
@Component
public class ServiceBImpl implements AbstractService<DemoB> {
@Override
public String serviceName() {
return "ServiceB";
}
@Override
public DemoB execute(Object parm) {
System.out.println("ServiceBImpl execute");
return new DemoB().setName("DemoB");
}
}
编写自定义Bean上下文
这里是重头戏,我们需要编写一个Bean上下文,并注入AbstractService集合。
@Component
public class ServiceContext {
// IService容器,key=serviceName,velue=实例
private static Map<String, AbstractService> SERVICE_CONTEXT;
@Autowired
List<AbstractService> services;
@PostConstruct
void init() {
SERVICE_CONTEXT = new ConcurrentHashMap<> ();
if (services == null) {
return;
}
// 将IService所有的实现类注册到serviceContext
for(AbstractService service : services) {
SERVICE_CONTEXT.put(service.serviceName(), service);
}
System.out.println(JSON.toJSONString(SERVICE_CONTEXT));
}
/**
* 根据serviceName获取实例
* @param serviceName
* @return
*/
public AbstractService getServiceImpl(String serviceName) {
return SERVICE_CONTEXT.get(serviceName);
}
}
其实注释已经很清楚了,首先定义一个Map,key为String,代表我们上文中接口返回的serviceName。
value为接口实现类bean实例。
接着通过@Autowired注入AbstractService集合,这里是一个List。当Spring容器初始化完成,会将AbstractService的实现类都加载到List中。
在@PostConstruct标记的初始化方法中,遍历 List<AbstractService>,并依次加载到我们初始化好的Map中。key=AbstractService.serviceName()的返回值,value为AbstractService实例。
定义一个getServiceImpl(String serviceName)提供给业务使用,能够让我们通过具体的serviceName标识获取到Bean实例。这也是为何serviceName不能重复的原因。
测试
到此主要的逻辑编写就完成了,我们编写一个测试类测试一下具体如何使用。
public static void main(String[] args) {
ConfigurableApplicationContext applicationContext = SpringApplication.run(DemoApplication.class, args);
// 获取bean Context
ServiceContext serviceContext = applicationContext.getBean("serviceContext", ServiceContext.class);
// 根据serviceName获取具体的接口实现类
AbstractService serviceA = serviceContext.getServiceImpl("ServiceA");
AbstractService serviceB = serviceContext.getServiceImpl("ServiceB");
// 调用service方法
serviceA.execute(null);
serviceB.execute(null);
}
这里从Spring上下文中获取到ServiceContext,并通过具体的serviceName获取到对应的Bean实例,并调用实例的execute方法。执行结果如下:
ServiceAImpl execute
ServiceBImpl execute
可能这还不算很直观,我们模拟一个业务场景。
业务需要先判断serviceName,再根据具体的值选择不同的执行逻辑。
正常情况下,我们会这样编写业务代码:
if ("ServiceA".equals(serviceName)) {
serviceA.execute()
return;
}
if ("ServiceB".equals(serviceName)) {
serviceB.execute()
return;
}
...
如果有一百个serviceName,那么这里就要有100个if分支,switch也同理。
但是采取本文中的编码方式则只需要这么写:
...省略获取serviceContext过程,最简单的方法是通过@Autowired/@Resource注入...
AbstractService service = serviceContext.getServiceImpl(serviceName);
service.execute()
这样我们就只需要在新增serviceName类型后,开发一个对应的实现类即可。
如果是传统的编码方式,则除了新增service实现,还需要修改if/switch判断逻辑,不够灵活且容易出错。
这里其实就是开放封闭原则的体现。传统的方式对修改和扩展都是开放的,而这种方式则是对扩展开放,对修改封闭的。尤其适用于复杂业务场景的开发。
原理
简单讲一下原理。
Spring框架支持对集合类型进行依赖注入,对于集合类型依赖注入与查找起作用的ApplicationContext实现类为 ListableBeanFactory。
我们看下源码是如何实现该特性的:
具体的逻辑在 org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency 这个方法中
打开该方法,重点关注下面这行
Object arg = beanFactory.resolveDependency(currDesc, beanName, autowiredBeans, typeConverter);
进入resolveDependency方法,看到下面这一行,跳入doResolveDependency方法
result = doResolveDependency(descriptor, requestingBeanName,
autowiredBeanNames, typeConverter);
重点关注下面的逻辑
Object multipleBeans = resolveMultipleBeans(descriptor, beanName, autowiredBeanNames, typeConverter);
if (multipleBeans != null) {
return multipleBeans;
}
此处的resolveMultipleBeans方法逻辑为,如果解析到了多个匹配条件的Bean,就直接返回解析结果。
那具体的解析结果又是什么呢?我们进入resolveMultipleBeans方法
private Object resolveMultipleBeans(DependencyDescriptor descriptor, @Nullable String beanName,
@Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) {
Class<?> type = descriptor.getDependencyType();
// 数组类型
if (type.isArray()) {
Class<?> componentType = type.getComponentType();
ResolvableType resolvableType = descriptor.getResolvableType();
Class<?> resolvedArrayType = resolvableType.resolve();
if (resolvedArrayType != null && resolvedArrayType != type) {
type = resolvedArrayType;
componentType = resolvableType.getComponentType().resolve();
}
if (componentType == null) {
return null;
}
Map<String, Object> matchingBeans = findAutowireCandidates(beanName, componentType,
new MultiElementDescriptor(descriptor));
if (matchingBeans.isEmpty()) {
return null;
}
if (autowiredBeanNames != null) {
autowiredBeanNames.addAll(matchingBeans.keySet());
}
TypeConverter converter = (typeConverter != null ? typeConverter : getTypeConverter());
Object result = converter.convertIfNecessary(matchingBeans.values(), type);
if (getDependencyComparator() != null && result instanceof Object[]) {
Arrays.sort((Object[]) result, adaptDependencyComparator(matchingBeans));
}
return result;
}
// 集合类型,如List set
else if (Collection.class.isAssignableFrom(type) && type.isInterface()) {
Class<?> elementType = descriptor.getResolvableType().asCollection().resolveGeneric();
if (elementType == null) {
return null;
}
Map<String, Object> matchingBeans = findAutowireCandidates(beanName, elementType,
new MultiElementDescriptor(descriptor));
if (matchingBeans.isEmpty()) {
return null;
}
if (autowiredBeanNames != null) {
autowiredBeanNames.addAll(matchingBeans.keySet());
}
TypeConverter converter = (typeConverter != null ? typeConverter : getTypeConverter());
Object result = converter.convertIfNecessary(matchingBeans.values(), type);
if (getDependencyComparator() != null && result instanceof List) {
((List<?>) result).sort(adaptDependencyComparator(matchingBeans));
}
return result;
}
// Map类型
else if (Map.class == type) {
ResolvableType mapType = descriptor.getResolvableType().asMap();
Class<?> keyType = mapType.resolveGeneric(0);
if (String.class != keyType) {
return null;
}
Class<?> valueType = mapType.resolveGeneric(1);
if (valueType == null) {
return null;
}
Map<String, Object> matchingBeans = findAutowireCandidates(beanName, valueType,
new MultiElementDescriptor(descriptor));
if (matchingBeans.isEmpty()) {
return null;
}
if (autowiredBeanNames != null) {
autowiredBeanNames.addAll(matchingBeans.keySet());
}
return matchingBeans;
}
else {
return null;
}
}
这里便是@Autowired注入集合类型的核心。
首先判断注入类型,如果是数组、Collection、Map等类型,则注入元素数据,即查找与元素类型相同的Bean,并注入到集合中。
这里重点强调下Map类型,我们能够看出,Map的 key 为Bean的 name,value 为 与定义的元素类型相同的Bean。
// Map的key
Class<?> keyType = mapType.resolveGeneric(0);
if (String.class != keyType) {
return null;
}
// Map的value
Class<?> valueType = mapType.resolveGeneric(1);
if (valueType == null) {
return null;
}
也就是说,如果业务上不依赖外部的type,那么我们可以直接注入一个Map集合,比如:
@Autowired
private Map<String, BeanInterface> map;
这样就能够将接口BeanInterface的实现都注入到Map中,key的值为具体Bean的name,value为Bean实例。
小结
本文中,我们通过案例与源码,全方位呈现了Spring对集合类型的注入方式。总结一下:
Spring在注入集合类的同时,会将集合泛型类的实例填入集合中,作为集合的初始值。
对于list、set填入的是注入类型Spring管理的实例,对于map,Spring会将service的名字作为key,对象作为value封装进入Map。
对于List类型,可以通过@Order指定加入List的顺序。只需要在实现类中加入@Order(value) 注解即可 ,值越小越先被初始化越先被放入List
-END-
更多精彩文章
4.撸个框架考虑一下?超万字长文,精讲如何手写分布式事务框架
6.面试官:小伙子,听说你看过ThreadLocal源码?万字图文深度解析ThreadLocal
如果你喜欢本文
请长按二维码,关注 Bella的技术轮子
转发至 朋友圈,是对我最大的支持
喜欢就点个在看吧